package com.github.fuse.handler;

import com.github.fuse.annotation.Fuse;
import com.github.fuse.annotation.Timeout;
import com.github.fuse.entity.AopExecute;
import com.github.fuse.exception.FuseException;
import org.apache.commons.lang3.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.concurrent.*;

/**
 * @author: wangdejun
 * @create: 2019-10-17 19:22
 **/
public class TimeoutFuseHandler extends AbstractFuseHandler<Timeout> {

    private ThreadPoolExecutor executor =
            new ThreadPoolExecutor(10, 20, 30,
                    TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread t = new Thread(r, "timeoutFuseHandler");
                    if (t.isDaemon())
                        t.setDaemon(false);
                    if (t.getPriority() != Thread.NORM_PRIORITY)
                        t.setPriority(Thread.NORM_PRIORITY);
                    return t;
                }
            });

    @Override
    public Class<? extends Annotation> getType() {
        return Timeout.class;
    }

    @Override
    public Object doHandler(AopExecute aopExecute, Timeout timeout) throws Throwable {
        Future<Object> submit = executor.submit(() -> {
            Object obj = null;
            try {
                obj = aopExecute.getJoinPoint().proceed(aopExecute.getParams());
            } catch (Throwable throwable) {
                throwable.printStackTrace();
                throw new RuntimeException(throwable);
            }
            return obj;
        });
        Object result = null;
        try {
            result = submit.get(timeout.timeout(), timeout.timeUnit());
        } catch (TimeoutException e) {
            // 取消任务执行
            submit.cancel(true);
            throw e;
        }
        return result;
    }

    @Override
    public Object fuseMethod(Throwable e, AopExecute aopExecute, Timeout timeout) throws Throwable {
        if (e instanceof TimeoutException) {
            String fuseMethod = timeout.fuseMethod();
            if (StringUtils.isBlank(fuseMethod)) {
                throw new FuseException("execute timeout", e);
            }
            Class<?> clazz = aopExecute.getEntity().getClass();
            Method method = clazz.getMethod(fuseMethod, aopExecute.getParamTypes());
            return method.invoke(aopExecute.getEntity(), aopExecute.getParams());
        }
        throw e;

    }

}
